//-------------------------------------------------
// OPath Query Engine
// Written by Jeff Lanning (jefflanning@gmail.com)
// Modeled after SDK for Longhorn CTP Build 4074
// Version 1: Dec 2004 - May 2005
//-------------------------------------------------
using System;
using System.Diagnostics;
using Wilson.ORMapper;
using Wilson.ORMapper.Internals;

namespace Wilson.ORMapper.Query
{
	/// <summary>
	/// Represents the OPath query language parser.
	/// </summary>
	internal class OPath //V2: Public class in Longhorn Spec
	{
		private Mappings _maps;

		internal OPath(Mappings maps)
		{
			_maps = maps;
		}

		/// <summary>
		/// Parses an OPath query into an ObjectExpression using the mappings from the specified ObjectSpace.
		/// </summary>
		/// <param name="query">OPathQuery to be processed.</param>
		/// <param name="os">ObjectSpaces containing the mappings to use.</param>
		/// <returns>ObjectExpression containing the parser results, which are ready to be compiled.</returns>
		public static ObjectExpression Parse(OPathQuery query, ObjectSpace os)
		{
			if( query == null ) throw new ArgumentNullException("query");
			if( os == null ) throw new ArgumentNullException("os");

			return Parse(query, os.context.Mappings);
		}

		internal static ObjectExpression Parse(OPathQuery query, Mappings maps)
		{
			OPath opath = new OPath(maps);

			int paramCount;
			Expression exp = opath.Parse(query.ObjectType, query.WhereExpression, query.SortExpression, out paramCount);
			ObjectExpression oe = new ObjectExpression(query.ObjectType, exp, maps, paramCount);
			oe.baseQuery = query;

			return oe;
		}

		internal Expression Parse(Type type, string query, string sort, out int paramCount)
		{
			EntityMap map = _maps[type];
			if( map == null )
			{
				throw new Exception("Type " + type + " does not have an entity mapping defined to the database.");
			}

			// get the expression tree from the parser
			Expression expr = new OPathParser().ParseObjectQuery(type, query, true, out paramCount);

			// wrap expression in an OrderBy node, if a sort order was specified
			if( sort != null && sort.Length > 0 )
			{
				OrderBy orderBy = new OPathSortParser(_maps).Parse(type, sort);
				orderBy.Source = expr;
				expr = orderBy;
			}

#if DEBUG_PARSER
			Debug.WriteLine("\n\nEXP: " + query);
			Debug.WriteLine("\nRAW PARSER TREE:\n" + expr.ToXmlString());
#endif

			// fix problems with the tree generated by the parser, normalizing the trees for equivalent expressions
			FixTree(expr);

#if DEBUG_PARSER
			Debug.WriteLine("\n\nEXP: " + query);
			Debug.WriteLine("\nFINAL EXPRESSION TREE:\n" + expr.ToXmlString());
#endif

			Validate(expr);

			return expr;
		}


		private void FixTree(Expression expr)
		{
			//Debug.WriteLine("\n\nBefore FixTree:\n" + expr.ToXmlString());

			//V2: This logic does not handle axis nodes in functions correctly.
			//V2: Need to crate a RecomposeFunction to move axis out from inside a function (or just don't let this happen - make them use []'s instead)
			//V2: Be sure to check that all properties in a function are of the same type.
			//	  Mixing relations in a function would be bad (e.g., LEN(BillAddress.Zip + ShipAddress.Zip)

			//V2: Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.RecomposeFunction), null, (Function)null);
			//V2: Debug.WriteLine("\n\nAfter RecomposeFunction:\n" + expr.ToXmlString());

			// note: using a post callback to handle binary node chains correctly (found this out the hard way)
			Expression.EnumNodes(expr, null, new Expression.EnumNodesCallBack(this.RecomposeBinary), null);
			//Debug.WriteLine("\n\nAfter RecomposeBinary:\n" + expr.ToXmlString());

			Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.ReplaceAxisCallback));
			//Debug.WriteLine("\n\nAfter ReplaceAxisCallback:\n" + expr.ToXmlString());

			Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.LinkPropertiesToMap), null, new object[0]);
			//Debug.WriteLine("\n\nAfter LinkPropertiesToMap:\n" + expr.ToXmlString());

			Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.ReplaceAddWithConcatenation), null, new object[0]);
			//Debug.WriteLine("\n\nAfter ReplaceAddWithConcatenation:\n" + expr.ToXmlString());

			//Debug.WriteLine("\n\nAfter FixTree:\n" + expr.ToXmlString());
		}

		private bool RecomposeBinary(Expression node, object[] noargs) // note: internal longhorn name found on www.winfx247.com
		{
			if( node.NodeType == NodeType.Binary )
			{
				Binary binary = (Binary)node;

				bool leftIsAxis = (binary.Left.NodeType == NodeType.Axis);
				bool rightIsAxis = (binary.Right.NodeType == NodeType.Axis);

				// nothing to do if neither branch contains an axis
				if( !leftIsAxis && !rightIsAxis )
				{
					return true;
				}

				// don't consider moving the binary node if neither branch is constant (and joined by a logical operator)
				// the binary node should stay above an axis in this case (example: (AAA.BBB = X) && (CCC = Y))
				if( !binary.Left.IsConst && !binary.Right.IsConst && Binary.IsOperatorLogical(binary.Operator) )
				{
					return true;
				}

				// see if we have an axis on both sides
				if( leftIsAxis && rightIsAxis )
				{
					throw new NotSupportedException("Relationship traversals on both sides of a binary operator is not currently supported.");
					// recompose the left and right to put both under one axis
					//V2: Recompose2Axis(binary);
					//return true;
				}
				else // axis on one side only
				{
					// get our root axis and find the last (leaf) axis in the chain
					// the leaf axis will hold the parent binary when moved
					// (this is to handle multiple dot expressions like: AAA.BBB.CCC = @Value)
					Axis axis = (leftIsAxis) ? (Axis)binary.Left : (Axis)binary.Right;
					Axis leafAxis = axis;
					int parentCount = 1;
					while( leafAxis.Constraint.NodeType == NodeType.Axis )
					{
						leafAxis = (Axis)leafAxis.Constraint;
						parentCount += 1;
					}

					// now make the binary node the constraint of the the leaf axis
					//   and move the existing constraint under the other branch of the binary
					if( leftIsAxis )
					{
						Expression newRight = (Expression)binary.Right.Clone();
						Expression.EnumNodes(newRight, new Expression.EnumNodesCallBack(this.IncreaseParentDepth), null, parentCount);
						leafAxis.Constraint = new Binary(binary.Operator, leafAxis.Constraint, newRight);
					}
					else // right is axis
					{
						Expression newLeft = (Expression)binary.Left.Clone();
						Expression.EnumNodes(newLeft, new Expression.EnumNodesCallBack(this.IncreaseParentDepth), null, parentCount);
						leafAxis.Constraint = new Binary(binary.Operator, newLeft, leafAxis.Constraint);
					}

					Expression.Replace(binary, axis);
				}
			}

			return true;
		}

		private bool IncreaseParentDepth(Expression node, object[] args)
		{
			if( node.NodeType == NodeType.Axis )
			{
				Axis axis = (Axis)node;
				IncreaseParentDepth((Property)axis.Source, args);
				return false;
			}
			else if( node.NodeType == NodeType.Property )
			{
				Property property = (Property)node;
				int parentCount = (int)args[0];
				for( int i = 0; i < parentCount; i++ )
				{
					property.Source = new Parent((Context)property.Source);
				}
				return false;
			}
			return true;
		}

		private bool ReplaceAxisCallback(Expression node, object[] noargs) // note: internal longhorn name found on www.winfx247.com
		{
			if( node.NodeType == NodeType.Axis )
			{
				Axis axis = (Axis)node;
				
				// convert the axis into a filter (don't want any axis nodes in final tree)
				Expression filter = new Filter(axis);

				// see if we need to wrap the filter with an exist node
				// note: this is needed when:
				//		1) the source property of the axis is relational
				//		2) the parent of the axis is not already an exists (for future support for the exists keyword)
				//		3) the axis is not in the source of its parent filter
				if( axis.Owner.NodeType != NodeType.Unary || ((Unary)axis.Owner).Operator != UnaryOperator.Exists )
				{
					// get the source property for this axis
					Property property;
					if( axis.Source.NodeType == NodeType.Property )
					{
						property = (Property)axis.Source;
					}
					else if( axis.Source.NodeType == NodeType.Filter || axis.Source.NodeType == NodeType.Axis )
					{
						property = (Property)(axis.Source as Filter).Source;
					}
					else // source not property, filter, or axis
					{
						throw new Exception("Axis source node type of '" + axis.Source.NodeType + "' was not expected.");
					}
				
					if( property.IsRelational )
					{
						// find the containing filter for this axis (there has to be one) and track the child
						Expression child = axis;
						Expression parent = axis.Owner;
						while( parent != null && parent.NodeType != NodeType.Filter )
						{
							child = parent;
							parent = parent.Owner;
						}
						if( parent == null )
						{
							throw new Exception("Axis node is not contained in a Filter node.  Assumption failed.");
						}
			
						if( ((Filter)parent).Source != child ) // axis not in source of filter
						{
							filter = new Unary(UnaryOperator.Exists, filter);
						}
					}
				}

				// do the replacement
				Expression.Replace(axis, filter);
			}
			return true;
		}

		private bool LinkPropertiesToMap(Expression node, object[] args)
		{
			if( node.NodeType == NodeType.Filter )
			{
				Filter filter = (Filter)node;
				Expression.EnumNodesCallBack callback = new Expression.EnumNodesCallBack(this.LinkPropertiesToMap);
				if( args.Length > 0 )
				{
					Expression.EnumNodes(filter.Source, callback, null, args);
				}

				// add the new filter to the font of the list
				object[] newArgs = new object[args.Length + 1];
				Array.Copy(args, 0, newArgs, 1, args.Length);
				newArgs[0] = filter;

				Expression.EnumNodes(filter.Constraint, callback, null, newArgs);
				return false;
			}
			else if( node.NodeType == NodeType.Property )
			{
				Property property = (Property)node;

				// determine the level of the filter to link
				int level = 0;
				if( property.Source.NodeType == NodeType.Parent )
				{
					level = (property.Source as Parent).Level;
					// replace the parent source with a new context for linking
					property.Source = new Context();
				}
				if( level >= args.Length )
				{
					throw new OPathException("Property '" + property.Name + "' could not be associated to an entity.");
				}

				// set the context link to the source of the filter
				Context context = (Context)property.Source;
				context.Link = (args[level] as Filter).Source;

				// set the property info
				SetPropertyInfo(property);
			}
			return true;
		}

		private bool ReplaceAddWithConcatenation(Expression node, object[] noargs) // note: internal longhorn name found on www.winfx247.com
		{
			if( node.NodeType == NodeType.Binary )
			{
				Binary binary = (Binary)node;
				if( binary.Operator == BinaryOperator.Addition && binary.Left.ValueType == typeof(string) && binary.Right.ValueType == typeof(string) )
				{
					binary.Operator = BinaryOperator.Concatenation;
				}
			}
			return true;
		}
		
		private void SetPropertyInfo(Property prop)
		{
			Expression link = (prop.Source as Context).Link;
			if( link.NodeType == NodeType.Filter )
			{
				Filter filter = (Filter)link;
				link = (filter.Source as Property);
			}
			if( link == null )
			{
				throw new Exception("Property '" + prop.Name + "' does not have a SourceLink node.");
			}

			Type ownerClass = null;
			if( link.NodeType == NodeType.TypeFilter )
			{
				TypeFilter node = (TypeFilter)link;
				ownerClass = node.Type;
			}
			else if( link.NodeType == NodeType.Property )
			{
				Property p = (Property)link;
				if( p.PropertyType == null )
				{
					SetPropertyInfo(p);
				}
				ownerClass = p.PropertyType;
			}
			else
			{
				throw new NotImplementedException("Property link of type '" + link.NodeType + "' was not expected.");
			}

			prop.OwnerClass = ownerClass;

			EntityMap entity = _maps[ownerClass];
			if( entity == null )
			{
				throw new Exception("Type " + ownerClass + " does not have an entity mapping defined to the database.");
			}

			if( prop.IsRelational )
			{
				RelationMap rel = entity.Relation(prop.Name);
				if( rel == null )
				{
					throw new Exception("Type '" + ownerClass + "' does not have a relationship named '" + prop.Name + "' in the entity map.");
				}
				prop.PropertyType = EntityMap.GetType(rel.Type);
				prop.RelationMap = rel;
			}
			else
			{
				FieldMap field = entity.GetFieldMap(prop.Name);
				if( field == null ) // NOTE: we never get a chance to throw our exception, get field map beats us to it
				{
					throw new Exception("Type '" + ownerClass + "' does not have a property named '" + prop.Name + "' in the entity map.");
				}
				prop.PropertyType = field.MemberType;
				prop.RelationMap = null;
			}
		}

		private void Recompose2Axis(Binary binary) // note: internal longhorn name found on www.winfx247.com
		{
			Axis left = (Axis)binary.Left;
			Axis right = (Axis)binary.Right;

			// find the leaf axis in the left chain
			// (this is to handle multiple dot expressions like: AAA.BBB.CCC.DDD)
			Axis leftLeaf = left;
			int leftDepth = 1;
			while( leftLeaf.Constraint.NodeType == NodeType.Axis )
			{
				leftLeaf = (Axis)leftLeaf.Constraint;
				leftDepth += 1;
			}

			// find the leaf axis in the right chain
			// (this is to handle multiple dot expressions like: AAA.BBB.CCC.DDD)
			Axis rightLeaf = left;
			int rightDepth = 1;
			while( rightLeaf.Constraint.NodeType == NodeType.Axis )
			{
				rightLeaf = (Axis)rightLeaf.Constraint;
				rightDepth += 1;
			}

			//V2: need to figure out how to get the leftLeaf.Constraint expression associated to the right parent after recomposing everything
			//		this may require us to setting all the property info before we can do 2-axis recompose
			// from 1-axis recompose
			//Expression newRight = (Expression)binary.Right.Clone();
			//Expression.EnumNodes(newRight, new Expression.EnumNodesCallBack(this.IncreaseParentDepth), null, parentCount);
			//leftLeaf.Constraint = new Binary(binary.Operator, leafAxis.Constraint, newRight);
		}

		
		private void Validate(Expression expr)
		{
			// validate every node in the tree
			Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.ValidateNode));
		}

		private bool ValidateNode(Expression node, object[] noargs)
		{
			node.Validate();
			return true;
		}
	}
}
